/*!
 * Copyright (c) 2010 Jacob Essex
 *
 * Dual licenced under MIT or GPL 3 licneces
 * 
 * See the GPL3 or MIT files in the main directory for details
 */

require.def('db/prediction', ['log/db', 'db', 'db/tags', 'chain'], 
function(Log, Db, Tags, Chain){

    var TagMapDb = function(db){
        var tbl = new Db.TableDef('TagMap', ['TagId', 'AnswerId', 'Inferred']);

        this.destroy = function(tx, funcs){
            var execSql = function(tx){
                tx.executeSql(
                    'DROP TABLE IF EXISTS ' + tbl.name,
                    [],
                    funcs && funcs.success ? function(tx,rs){funcs.success(tx);} : function(){},
                    funcs && funcs.error ? function(tx, e){funcs.error(e.message);} : function(){}
                );
            };
            if ( tx ){
                execSql(tx);
            }else{
                db.transaction(execSql);
            }
        };

        this.create = function(tx, funcs){
            var execSql = function(tx){
                var sql = 'CREATE TABLE IF NOT EXISTS ' + tbl.name + ' ('
                        + 'Id integer, TagId integer, AnswerId integer,' 
                        + 'Inferred integer,' 
                        + 'PRIMARY KEY(Id)'
                        + ')';
                tx.executeSql(
                    sql,
                    [],
                    funcs && funcs.success ? function(tx,rs){funcs.success(tx);} : function(){},
                    funcs && funcs.error ? function(tx, e){funcs.error(e.message);} : function(){}
                );
            };
            if ( tx ){
                execSql(tx);
            }else{
                db.transaction(execSql);
            }
        }

        this.createLink = function(tx, funcs){
            var l = new TagLink(db, tbl, Db.ROW_NOT_CREATED);
            funcs.success && funcs.success(tx, l);
        }


    };

    function TagLink(db, tbl, id){
        this.__proto__ = new Db.Row(this,db, tbl, id);

        this.TagId = 0;
        this.AnswerId = 0;
        this.Inferred = 0;
    }

    function PredictionAnswerData(db, tbl, id){
        this.__proto__ = new Db.Row(this,db, tbl, id);
        var that = this;

        this.SeAnswerId = 0;
        this.SeQuestionId = 0;
        this.QuestionTags = [];

        //inferred tags are from things like the title
        this.InferredTags = [];

        this.UpVotes = 0;
        this.DownVotes = 0;

        /**
         * This is missnamed. It should be createTagLink
         * @todo rename
         */
        var addTag = function(tx, answerId, tagId, inferred, callbacks){
            var tm = new TagMapDb(db);
            tm.createLink(tx,{
                success:function(tx, link){
                    link.Inferred = inferred;
                    link.AnswerId = answerId;
                    link.TagId = tagId;
                    link.save(tx,callbacks); 
                }
                //cannot fail, so no need for error check here
            });
        };
        
        var createTag = function(tx, answerId, tagName, inferred, callbacks){
            Db.chainWith(db, tx)
              .queue(Tags.TagsDb, 'exists', [tagName], 'yes')
              .queue(Tags.TagsDb,
                    'getByTagName',
                    [tagName],
                    'success',
                    function(tx, tag){
                        Log.info('adding to tagmap ' + tagName);
                        addTag(tx,answerId, tag.getId(), inferred, {success:callbacks.success});
                        return true;
                    })
              .error(function(e){ callbacks && callbacks.error && callbacks.error(e)}) 
              .exec();
            //don't create tags if the tag is inferred. We would end up with words
            //like "the" as tags which is pointless
            if ( inferred === false ){
                Db.chainWith(db, tx)
                  .queue(Tags.TagsDb, 'exists', [tagName], 'no')
                  .queue(Tags.TagsDb, 'createTag', [], 'success', function(tx, tag){
                          Log.info('creating tag ' + tagName);
                          tag.Name = tagName;
                          tag.save(tx, { //we must wait till it is saved as we get the tag di
                              success:function(){
                                Log.info('adding to tagmap ' + tagName);
                                addTag(tx,answerId, tag.getId(), inferred, {success:callbacks.success});
                                //addTag(tx, answerId, tag.getId(), inferred, {});//callbacks);
                                return true;
                              }
                          });
                      })
                  .error(function(e){ callbacks && callbacks.error && callbacks.error(e)}) 
                  .exec();
            }
            else{
                Db.chainWith(db, tx)
                  .queue(Tags.TagsDb, 'exists', [tagName], 'no')
                  .success(callbacks.success)
                  .exec();
            }
        };
        /*
         * This function doesn't work fully, but it is far better than the current one
         * if it worked...
         */
        /*
        var createTag = function(tx, answerId, tagName, inferred, callbacks){
            if ( callbacks == undefined ){
                callbacks = {};
            }
            callbacks.success = callbacks.success ? callbacks.success : function(){};
            callbacks.error = callbacks.error ? callbacks.error : function(){};

            //two levels of chaining, the chain functions add to the worker functions. when all the functions
            //in chain are called, the worker is run which then calls the success functions. Yay
            var chain = Chain.create();
            var worker = Chain.create();

            worker.error(callbacks.error);
            worker.error(function(){alert('done')});

            chain.queue(function(callbacks){
                Db.chainWith(db)
                  .queue(Tags.TagsDb, 'exists', [tagName], 'yes')
                  .queue(Tags.TagsDb,
                        'getByTagName',
                        [tagName],
                        'success',
                        function(tx, tag){
                            Log.info('adding to tagmap ' + tagName);
                            //tx is null as we don't need to use the same transaction here. It doesn't matter when the tag is created
                            //as it won't conflict with anything.
                            worker.queue(addTag, [null,answerId, tag.getId(), inferred], 'success');
                            return true;
                        })
                  .error(callbacks.error) 
                  .unexpected(callbacks.success)
                  .success(callbacks.success)
                  .exec();
            }, [], 'success');
            chain.queue(function(callbacks){
                //don't create tags if the tag is inferred. We would end up with words
                //like "the" as tags which is pointless
                if ( inferred === false ){
                    Db.chainWith(db)
                      .queue(Tags.TagsDb, 'exists', [tagName], 'no')
                      .queue(Tags.TagsDb, 'createTag', [], 'success', function(tx, tag){
                              Log.info('creating tag ' + tagName);
                              tag.Name = tagName;
                              tag.save(tx, { //we must wait till it is saved as we get the tag di
                                  success:function(){
                                    Log.info('adding to tagmap ' + tagName);
                                    worker.queue(addTag, [null,answerId, tag.getId(), inferred], 'success');
                                  }
                              });
                              return true;
                          })
                      .error(callbacks.error) 
                      .unexpected(callbacks.success)
                      .success(callbacks.success)
                      .exec();
                }
            }, [], 'success');

            //run the code that adds to the worker
            chain.error(callbacks.error)
                 .success(function(){ worker.exec(); })
                 .exec();
        };
        */

        this.save = function(tx, callbacks){
            //have to save before we create tags to ensure we
            //have the id of the answer

            //this should simplify things... should
            callbacks = callbacks == undefined ? {} : callbacks;
            var c = callbacks;
            callbacks.error = c.error == undefined ? function(){} : c.error;

            //store the origanal success function
            var success = c.success == undefined ? function(){} : c.success;

            
            //all the other functions for save are the same, but the success function does extra work
            //by creating the required tags
            callbacks.success = function(tx){
                var ansId = that.getId();
                var c = Chain.create(); //must chain, otherwise we call success too soon
                for ( var i = 0; i < that.QuestionTags.length; i++ ){
                    c.queue(createTag, [tx, ansId, that.QuestionTags[i], false], 'success');
                }
                for ( var i = 0; i < that.InferredTags.length; i++ ){
                    c.queue(createTag, [tx, ansId, that.InferredTags[i], true], 'success');
                }
                c.error(callbacks.error)
                 .success(success)
                 .exec();
            }

            this.__proto__.save(tx,callbacks);

        }
    }
    return{
        'TagMapDb':TagMapDb,


        /**
         * This is a wrapper around the database that holds the data for predications
         * 
         */
        PredictionDb:function(db){
            var tbl = new Db.TableDef('AnswerDetails', ['SeAnswerId', 'SeQuestionId', 'UpVotes', 'DownVotes']);

            this.destroy = function(tx, funcs){
                var execSql = function(tx){
                    tx.executeSql(
                        'DROP TABLE IF EXISTS ' + tbl.name,
                        [],
                        funcs && funcs.success ? function(tx,rs){funcs.success(tx);} : function(){},
                        funcs && funcs.error ? function(tx, e){funcs.error(e.message);} : function(){}
                    );
                };
                if ( tx ){
                    execSql(tx);
                }else{
                    db.transaction(execSql);
                }
            };

            this.create = function(tx, funcs){
                var execSql = function(tx){
                    var sql = 'CREATE TABLE IF NOT EXISTS AnswerDetails (' 
                            + 'Id integer, SeAnswerId integer, SeQuestionId integer,'
                            + 'UpVotes integer, DownVotes integer,'
                            + 'PRIMARY KEY(Id)'
                            + ')';
                    tx.executeSql(
                        sql,
                        [],
                        funcs && funcs.success ? function(tx,rs){funcs.success(tx);} : function(){},
                        funcs && funcs.error ? function(tx, e){funcs.error(e.message);} : function(){}
                    );
                };
                if ( tx ){
                    execSql(tx);
                }else{
                    db.transaction(execSql);
                }
            }

            this.createAnswer = function(tx, funcs){
                var a = new PredictionAnswerData(db, tbl, Db.ROW_NOT_CREATED);
                funcs && funcs.success && funcs.success(tx, a);
            };


            this.count = function(tx, funcs){
                var execSql = function(tx){
                    Db.site.count(tx, tbl.name, [], [], funcs);
                };
                if ( tx ){
                    execSql(tx);
                }else{
                    db.transaction(execSql);
                }
            }

            this.getAnswers = function(tx, funcs){
                if ( funcs === undefined ){
                    return;
                }
                //TODO refactor
                var execSql = function(tx){
                    var sql = 'SELECT * FROM ' + tbl.name;
                    tx.executeSql(
                            sql,
                            [],
                            function(tx, rs){
                                if ( rs.rows.length == 0 ){
                                    funcs.notFound && funcs.notFound(tx);
                                    return;
                                }
                                var items = [];
                                //copy and paste with above
                                for ( var i = 0; i < rs.rows.length; i++ ){
                                    //function to avoid closure issue. (I think)
                                    (function(row){
                                        var id = row['Id'];
                                        var obj = new PredictionAnswerData(db, tbl, id); 
                                        tbl.columns.forEach(function(k){ obj[k] = row[k]; }); //transfer properties

                                        sql = 'SELECT * '
                                            + 'FROM TagMap, Tags '
                                            + 'WHERE TagMap.TagId = Tags.Id '
                                            +   'AND TagMap.AnswerId = ?'
                
                                        tx.executeSql(sql, [id],
                                            function(tx, tagRs){
                                                obj.QuestionTags = [];
                                                obj.InferredTags = [];

                                                for ( var i = 0; i < tagRs.rows.length; i++ ){
                                                    var row = tagRs.rows.item(i);
                                                    if ( row.Inferred == 1 ){
                                                        obj.InferredTags.push(row.Name);
                                                    }else{
                                                        obj.QuestionTags.push(row.Name);
                                                    }
                                                }
                                                items.push(obj);
                                                if ( items.length == rs.rows.length ){
                                                    funcs.success && funcs.success(tx, items);
                                                }
                                            },
                                            function(tx, err){
                                                funcs.error && funcs.error(err.message);
                                            }
                                        );
                                    })(rs.rows.item(i));
                                     

                                }
                            },
                            function(tx, err){
                                funcs.error && funcs.error(err.message);
                            }
                    );
                };
                if ( tx ){
                    execSql(tx);
                }else{
                    db.transaction(execSql);
                }
            }
        }
    }
});

